import tkinter as tk
from tkinter import filedialog, messagebox, ttk, font, simpledialog, colorchooser
import os
import datetime
import threading
import time
import re
import shutil
import json
import base64
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import padding
import random
import string
import hashlib

class AESCipher:
    """增强版AES-256加密解密工具类，确保加密后内容为乱码"""
    def __init__(self, key):
        # 使用SHA-256处理密钥，确保得到32字节(256位)的密钥
        self.key = hashlib.sha256(key.encode()).digest()
        self.backend = default_backend()
        # 添加文件标识，用于区分加密文件
        self.file_identifier = b'ENC_NOTE\x00\x01'  # 自定义文件标识

    def encrypt(self, data):
        # 生成16字节的随机IV
        iv = os.urandom(16)
        # 创建加密器
        cipher = Cipher(algorithms.AES(self.key), modes.CBC(iv), backend=self.backend)
        encryptor = cipher.encryptor()
        
        # 对数据进行填充，使其长度为16字节的倍数
        padder = padding.PKCS7(128).padder()
        padded_data = padder.update(data.encode('utf-8')) + padder.finalize()
        
        # 加密
        encrypted_data = encryptor.update(padded_data) + encryptor.finalize()
        
        # 组合文件标识、IV和加密数据，确保加密后内容不可读
        combined = self.file_identifier + iv + encrypted_data
        
        # 使用自定义编码而非标准Base64，避免部分可识别性
        return self.custom_b64encode(combined)

    def decrypt(self, encrypted_data):
        try:
            # 解码
            combined = self.custom_b64decode(encrypted_data)
            
            # 验证文件标识
            if not combined.startswith(self.file_identifier):
                raise ValueError("不是有效的加密文件")
                
            # 提取IV（标识后16字节）
            iv = combined[len(self.file_identifier):len(self.file_identifier)+16]
            encrypted_data = combined[len(self.file_identifier)+16:]
            
            # 创建解密器
            cipher = Cipher(algorithms.AES(self.key), modes.CBC(iv), backend=self.backend)
            decryptor = cipher.decryptor()
            
            # 解密
            padded_data = decryptor.update(encrypted_data) + decryptor.finalize()
            
            # 去除填充
            unpadder = padding.PKCS7(128).unpadder()
            data = unpadder.update(padded_data) + unpadder.finalize()
            
            return data.decode('utf-8')
        except Exception as e:
            # 解密失败，可能是密码错误或文件损坏
            raise ValueError(f"解密失败: {str(e)}")

    def custom_b64encode(self, data):
        """自定义Base64编码，替换部分字符以增强乱码效果"""
        b64 = base64.b64encode(data)
        # 替换部分字符，破坏标准Base64格式
        trans_table = str.maketrans('+/=', '-_$')
        return b64.decode('utf-8').translate(trans_table)

    def custom_b64decode(self, data):
        """自定义Base64解码，还原被替换的字符"""
        # 还原被替换的字符
        trans_table = str.maketrans('-_$', '+/=')
        normalized = data.translate(trans_table)
        return base64.b64decode(normalized)

class LoginWindow:
    """登录窗口类"""
    def __init__(self, parent, notebook_app):
        self.parent = parent
        self.notebook_app = notebook_app
        self.login_window = tk.Toplevel(parent)
        self.login_window.title("登录")
        self.login_window.geometry("300x150")
        self.login_window.transient(parent)
        self.login_window.grab_set()
        self.login_window.resizable(False, False)
        
        # 确保关闭登录窗口时退出程序
        self.login_window.protocol("WM_DELETE_WINDOW", parent.destroy)
        
        # 创建登录界面
        tk.Label(self.login_window, text="请输入密码登录", font=("SimSun", 12)).pack(pady=10)
        
        self.password_var = tk.StringVar()
        password_frame = ttk.Frame(self.login_window)
        password_frame.pack(pady=5, padx=20, fill=tk.X)
        
        ttk.Label(password_frame, text="密码:").pack(side=tk.LEFT)
        self.password_entry = ttk.Entry(password_frame, textvariable=self.password_var, show="*")
        self.password_entry.pack(side=tk.RIGHT, fill=tk.X, expand=True, padx=5)
        self.password_entry.focus_set()
        
        # 绑定Enter键登录
        self.login_window.bind('<Return>', lambda event: self.login())
        
        ttk.Button(self.login_window, text="登录", command=self.login).pack(pady=15)
        
        self.login_success = False

    def login(self):
        """验证登录密码"""
        password = self.password_var.get()
        if self.notebook_app.verify_password(password):
            # 登录成功，初始化加密器
            self.notebook_app.cipher = AESCipher(password)
            self.login_success = True
            self.login_window.destroy()
        else:
            messagebox.showerror("登录失败", "密码不正确，请重新输入")
            self.password_entry.delete(0, tk.END)
            self.password_entry.focus_set()

class NoteBook:
    def __init__(self, root):
        self.root = root
        self.root.title("增强版记事本")
        self.root.geometry("1000x600")
        
        # 应用数据目录
        self.app_data_dir = os.path.join(os.path.expanduser("~"), ".enhanced_notebook")
        os.makedirs(self.app_data_dir, exist_ok=True)
        
        # 加密相关
        self.cipher = None
        self.password_hash = None
        self.password_file = os.path.join(self.app_data_dir, "password.hash")
        
        # 登录状态
        self.logged_in = False
        
        # 本地存储路径
        self.storage_path = tk.StringVar(value=os.path.join(os.path.expanduser("~"), "Documents", "我的笔记"))
        os.makedirs(self.storage_path.get(), exist_ok=True)
        
        # 备份设置
        self.backup_enabled = tk.BooleanVar(value=True)
        self.backup_interval = tk.StringVar(value="daily")  # daily, weekly
        self.backup_dir = os.path.join(self.app_data_dir, "backups")
        os.makedirs(self.backup_dir, exist_ok=True)
        
        # 回收站设置
        self.recycle_bin = os.path.join(self.app_data_dir, "recycle_bin")
        os.makedirs(self.recycle_bin, exist_ok=True)
        self.recycle_info = os.path.join(self.recycle_bin, ".recycle_info.json")  # 存储删除时间等信息
        
        # 创建基础字体
        self.base_font = font.Font(family="SimSun", size=12)
        
        # 排序相关变量
        self.sort_type = tk.StringVar(value="create_time_desc")  # 创建时间降序
        
        # 格式设置相关变量
        self.font_family = tk.StringVar(value="SimSun")  # 字体
        self.font_size = tk.IntVar(value=12)            # 字号
        self.font_color = tk.StringVar(value="#000000")   # 字体颜色，默认为黑色
        self.bold = tk.BooleanVar(value=False)          # 粗体
        self.italic = tk.BooleanVar(value=False)        # 斜体
        self.underline = tk.BooleanVar(value=False)     # 下划线
        
        # 初始化设置窗口变量为None
        self.storage_settings_window = None
        self.backup_settings_window = None
        self.auto_save_window = None
        self.recycle_bin_window = None
        
        # 拖拽相关变量
        self.dragging = False
        self.dragged_item = None
        
        # 检查加密密码是否已设置
        self.check_encryption_password()
        
        # 显示登录窗口
        self.show_login_window()
        
        # 如果登录成功，继续初始化
        if self.logged_in:
            # 创建UI组件
            self.create_main_layout()
            
            # 然后再加载设置和执行其他操作
            self.load_storage_settings()
            self.load_backup_settings()
            
            # 配置查找高亮标签
            self.text.tag_configure("search_highlight", background="yellow")
            
            # 配置格式标签
            self.text.tag_configure("bold", font=(self.base_font.actual()["family"], 
                                                 self.base_font.actual()["size"], "bold"))
            self.text.tag_configure("italic", font=(self.base_font.actual()["family"], 
                                                  self.base_font.actual()["size"], "italic"))
            self.text.tag_configure("underline", underline=True)
            
            # 当前打开的文件路径
            self.current_file = None
            # 自动保存相关变量
            self.auto_save_enabled = tk.BooleanVar(value=True)
            self.auto_save_interval = tk.IntVar(value=5)  # 默认为5分钟
            self.auto_save_thread = None
            self.running = False
            
            # 新增功能相关变量
            self.clipboard_history = []
            self.max_clip_history = 10  # 最多保存10条剪贴板历史
            self.column_select = False  # 列选模式标记
            self.start_x, self.start_y = 0, 0  # 鼠标起始位置
            self.find_window = None  # 查找替换窗口
            self.find_pos = "1.0"  # 查找起始位置
            
            # 绑定快捷键
            self.bind_shortcuts()
            
            # 绑定窗口关闭事件
            self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
            
            # 创建菜单
            self.create_menu()
            
            # 启动自动保存、备份和回收站清理
            self.start_auto_save()
            self.start_backup_monitor()
            self.start_recycle_bin_cleaner()

    def show_login_window(self):
        """显示登录窗口"""
        login = LoginWindow(self.root, self)
        self.root.wait_window(login.login_window)
        self.logged_in = login.login_success
        
        # 如果登录失败，退出程序
        if not self.logged_in:
            self.root.destroy()

    def create_main_layout(self):
        """创建主界面布局，左侧为目录树，右侧为文本编辑区"""
        # 创建主分割窗格
        self.main_paned = ttk.PanedWindow(self.root, orient=tk.HORIZONTAL)
        self.main_paned.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        
        # 左侧目录树框架
        self.tree_frame = ttk.Frame(self.main_paned, width=200)
        self.main_paned.add(self.tree_frame, weight=1)
        
        # 目录树控制按钮
        self.tree_controls = ttk.Frame(self.tree_frame)
        self.tree_controls.pack(fill=tk.X, padx=2, pady=2)
        
        ttk.Button(self.tree_controls, text="新建文件夹", command=self.create_new_folder).pack(side=tk.LEFT, padx=2)
        ttk.Button(self.tree_controls, text="刷新", command=self.refresh_directory_tree).pack(side=tk.LEFT, padx=2)
        
        # 排序选择下拉框
        ttk.Label(self.tree_controls, text="排序:").pack(side=tk.LEFT, padx=2)
        sort_options = [
            ("创建时间(新→旧)", "create_time_desc"),
            ("创建时间(旧→新)", "create_time_asc"),
            ("修改时间", "modify_time"),
            ("标题", "title")
        ]
        self.sort_combobox = ttk.Combobox(self.tree_controls, textvariable=self.sort_type, state="readonly", width=15)
        self.sort_combobox['values'] = [opt[0] for opt in sort_options]
        self.sort_combobox.current(0)
        self.sort_combobox.bind("<<ComboboxSelected>>", lambda e: self.refresh_directory_tree())
        self.sort_combobox.pack(side=tk.LEFT, padx=2)
        
        # 批量操作按钮
        ttk.Button(self.tree_controls, text="批量操作", command=self.show_batch_operations).pack(side=tk.RIGHT, padx=2)
        
        # 创建目录树
        self.directory_tree = ttk.Treeview(self.tree_frame)
        self.directory_tree.pack(fill=tk.BOTH, expand=True, padx=2, pady=2)
        
        # 添加滚动条
        tree_scroll = ttk.Scrollbar(self.directory_tree, orient="vertical", command=self.directory_tree.yview)
        tree_scroll.pack(side=tk.RIGHT, fill=tk.Y)
        self.directory_tree.configure(yscrollcommand=tree_scroll.set)
        
        # 绑定目录树事件
        self.directory_tree.bind("<Double-1>", self.on_tree_item_double_click)
        self.directory_tree.bind("<Button-3>", self.show_tree_context_menu)
        
        # 拖拽相关事件绑定
        self.directory_tree.bind("<ButtonPress-1>", self.on_drag_start)
        self.directory_tree.bind("<B1-Motion>", self.on_drag_motion)
        self.directory_tree.bind("<ButtonRelease-1>", self.on_drag_end)
        
        # 创建右键菜单
        self.create_tree_context_menu()
        
        # 初始化目录树
        self.refresh_directory_tree()
        
        # 右侧文本编辑区
        self.text_frame = ttk.Frame(self.main_paned)
        self.main_paned.add(self.text_frame, weight=3)
        
        # 文本编辑区域
        self.text = tk.Text(self.text_frame, undo=True, font=self.base_font, fg=self.font_color.get())
        self.text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        self.text.focus_set()

    def on_drag_start(self, event):
        """拖拽开始事件处理"""
        # 获取被点击的项目
        item = self.directory_tree.identify_row(event.y)
        if item:
            self.dragging = True
            self.dragged_item = item
            # 选中被拖拽的项目
            self.directory_tree.selection_set(item)

    def on_drag_motion(self, event):
        """拖拽移动事件处理"""
        if self.dragging:
            # 获取当前鼠标位置下的项目
            target_item = self.directory_tree.identify_row(event.y)
            if target_item and target_item != self.dragged_item:
                # 检查目标是否为目录
                target_type = self.directory_tree.item(target_item, "values")[1]
                if target_type == "directory":
                    # 高亮显示目标目录
                    self.directory_tree.selection_set(target_item)

    def on_drag_end(self, event):
        """拖拽结束事件处理"""
        if self.dragging and self.dragged_item:
            # 获取释放位置下的项目
            target_item = self.directory_tree.identify_row(event.y)
            
            # 如果释放到了一个有效的目标目录上
            if target_item and target_item != self.dragged_item:
                target_type = self.directory_tree.item(target_item, "values")[1]
                if target_type == "directory":
                    # 获取源项目和目标项目的路径
                    source_path = self.directory_tree.item(self.dragged_item, "values")[0]
                    target_path = self.directory_tree.item(target_item, "values")[0]
                    
                    # 确保源和目标不是同一个目录
                    if os.path.abspath(source_path) != os.path.abspath(target_path):
                        # 执行移动操作
                        self.perform_drag_move(source_path, target_path)
            
            # 重置拖拽状态
            self.dragging = False
            self.dragged_item = None
            # 恢复选中状态
            self.directory_tree.selection_set(self.dragged_item)

    def perform_drag_move(self, source_path, target_path):
        """执行拖拽移动操作"""
        try:
            # 构建目标路径
            source_name = os.path.basename(source_path)
            destination = os.path.join(target_path, source_name)
            
            # 检查目标路径是否已存在
            if os.path.exists(destination):
                if not messagebox.askyesno("确认覆盖", f"目标位置已存在{source_name}，是否覆盖?"):
                    return
            
            # 执行移动
            shutil.move(source_path, destination)
            
            # 刷新目录树
            self.refresh_directory_tree()
            
            # 如果移动的是当前打开的文件，更新当前文件路径
            if self.current_file == source_path:
                self.current_file = destination
                self.root.title(f"增强版记事本 - {os.path.basename(destination)}")
                
        except Exception as e:
            messagebox.showerror("错误", f"移动失败: {str(e)}")

    def create_tree_context_menu(self):
        """创建目录树的右键菜单"""
        self.tree_context_menu = tk.Menu(self.root, tearoff=0)
        self.tree_context_menu.add_command(label="新建文件夹", command=self.create_new_folder)
        self.tree_context_menu.add_command(label="新建笔记", command=self.create_new_note)
        self.tree_context_menu.add_separator()
        self.tree_context_menu.add_command(label="重命名", command=self.rename_tree_item)
        self.tree_context_menu.add_command(label="移动", command=self.move_tree_item)
        self.tree_context_menu.add_command(label="删除", command=self.delete_tree_item)
        self.tree_context_menu.add_separator()
        self.tree_context_menu.add_command(label="属性", command=self.show_item_properties)

    def show_tree_context_menu(self, event):
        """显示右键菜单"""
        # 选中点击的项目
        item = self.directory_tree.identify_row(event.y)
        if item:
            self.directory_tree.selection_set(item)
            self.directory_tree.focus(item)
            # 显示菜单
            self.tree_context_menu.post(event.x_root, event.y_root)

    def refresh_directory_tree(self):
        """刷新目录树"""
        # 确保directory_tree已创建
        if not hasattr(self, 'directory_tree'):
            return
            
        # 清空现有内容
        for item in self.directory_tree.get_children():
            self.directory_tree.delete(item)
        
        # 添加根目录
        root_path = self.storage_path.get()
        root_node = self.directory_tree.insert("", tk.END, text=os.path.basename(root_path), 
                                              values=[root_path, "directory"])
        
        # 递归添加子目录和文件
        self.populate_tree(root_node, root_path)

    def populate_tree(self, parent, path):
        """递归填充目录树"""
        try:
            # 获取目录内容
            items = os.listdir(path)
            
            # 根据排序类型排序
            if self.sort_type.get() == "create_time_desc":
                items.sort(key=lambda x: os.path.getctime(os.path.join(path, x)), reverse=True)
            elif self.sort_type.get() == "create_time_asc":
                items.sort(key=lambda x: os.path.getctime(os.path.join(path, x)))
            elif self.sort_type.get() == "modify_time":
                items.sort(key=lambda x: os.path.getmtime(os.path.join(path, x)), reverse=True)
            elif self.sort_type.get() == "title":
                items.sort(key=lambda x: x.lower())
            
            for item in items:
                item_path = os.path.join(path, item)
                try:
                    if os.path.isdir(item_path):
                        # 插入目录
                        node = self.directory_tree.insert(parent, tk.END, text=item, 
                                                       values=[item_path, "directory"])
                        # 递归添加子目录
                        self.populate_tree(node, item_path)
                    else:
                        # 只显示文本文件
                        if item.lower().endswith(('.txt', '.md')):
                            self.directory_tree.insert(parent, tk.END, text=item, 
                                                     values=[item_path, "file"])
                except Exception as e:
                    print(f"无法访问 {item_path}: {e}")
        except Exception as e:
            print(f"无法访问 {path}: {e}")

    def on_tree_item_double_click(self, event):
        """双击树项目事件处理"""
        item = self.directory_tree.selection()[0]
        item_type = self.directory_tree.item(item, "values")[1]
        item_path = self.directory_tree.item(item, "values")[0]
        
        if item_type == "file":
            self.open_file_from_path(item_path)
        # 如果是目录，不做处理，目录树已经可以展开/折叠

    def create_new_folder(self):
        """创建新文件夹"""
        # 获取选中的项目作为父目录
        try:
            selected_item = self.directory_tree.selection()[0]
            selected_type = self.directory_tree.item(selected_item, "values")[1]
            parent_path = self.directory_tree.item(selected_item, "values")[0]
            
            # 如果选中的是文件，则使用其父目录
            if selected_type == "file":
                parent_path = os.path.dirname(parent_path)
        except:
            # 如果没有选中项目，使用根目录
            parent_path = self.storage_path.get()
        
        # 请求文件夹名称
        folder_name = simpledialog.askstring("新建文件夹", "请输入文件夹名称:")
        if folder_name and folder_name.strip():
            folder_path = os.path.join(parent_path, folder_name.strip())
            try:
                os.makedirs(folder_path, exist_ok=False)
                self.refresh_directory_tree()
            except Exception as e:
                messagebox.showerror("错误", f"创建文件夹失败: {str(e)}")

    def create_new_note(self):
        """创建新笔记"""
        # 获取选中的项目作为父目录
        try:
            selected_item = self.directory_tree.selection()[0]
            selected_type = self.directory_tree.item(selected_item, "values")[1]
            parent_path = self.directory_tree.item(selected_item, "values")[0]
            
            # 如果选中的是文件，则使用其父目录
            if selected_type == "file":
                parent_path = os.path.dirname(parent_path)
        except:
            # 如果没有选中项目，使用根目录
            parent_path = self.storage_path.get()
        
        # 请求笔记名称
        note_name = simpledialog.askstring("新建笔记", "请输入笔记名称(无需扩展名):")
        if note_name and note_name.strip():
            note_path = os.path.join(parent_path, f"{note_name.strip()}.txt")
            try:
                # 创建空文件
                with open(note_path, "w", encoding="utf-8") as f:
                    pass
                self.refresh_directory_tree()
                # 打开新创建的笔记
                self.open_file_from_path(note_path)
            except Exception as e:
                messagebox.showerror("错误", f"创建笔记失败: {str(e)}")

    def rename_tree_item(self):
        """重命名选中的项目"""
        try:
            selected_item = self.directory_tree.selection()[0]
            item_path = self.directory_tree.item(selected_item, "values")[0]
            item_name = os.path.basename(item_path)
            
            # 请求新名称
            new_name = simpledialog.askstring("重命名", "请输入新名称:", initialvalue=item_name)
            if new_name and new_name.strip() and new_name != item_name:
                new_path = os.path.join(os.path.dirname(item_path), new_name.strip())
                try:
                    os.rename(item_path, new_path)
                    self.refresh_directory_tree()
                    
                    # 如果重命名的是当前打开的文件，更新当前文件路径
                    if self.current_file == item_path:
                        self.current_file = new_path
                        self.root.title(f"增强版记事本 - {os.path.basename(new_path)}")
                except Exception as e:
                    messagebox.showerror("错误", f"重命名失败: {str(e)}")
        except:
            messagebox.showwarning("警告", "请先选择一个项目")

    def move_tree_item(self):
        """移动选中的项目"""
        try:
            selected_item = self.directory_tree.selection()[0]
            item_path = self.directory_tree.item(selected_item, "values")[0]
            item_name = os.path.basename(item_path)
            
            # 选择目标目录
            target_dir = filedialog.askdirectory(title="选择目标目录", initialdir=self.storage_path.get())
            if target_dir and target_dir != os.path.dirname(item_path):
                target_path = os.path.join(target_dir, item_name)
                
                # 检查目标路径是否存在
                if os.path.exists(target_path):
                    if not messagebox.askyesno("确认覆盖", f"目标位置已存在{item_name}，是否覆盖?"):
                        return
                
                try:
                    shutil.move(item_path, target_path)
                    self.refresh_directory_tree()
                    
                    # 如果移动的是当前打开的文件，更新当前文件路径
                    if self.current_file == item_path:
                        self.current_file = target_path
                        self.root.title(f"增强版记事本 - {os.path.basename(target_path)}")
                except Exception as e:
                    messagebox.showerror("错误", f"移动失败: {str(e)}")
        except:
            messagebox.showwarning("警告", "请先选择一个项目")

    def delete_tree_item(self):
        """删除选中的项目到回收站"""
        try:
            selected_item = self.directory_tree.selection()[0]
            item_path = self.directory_tree.item(selected_item, "values")[0]
            item_name = os.path.basename(item_path)
            
            # 确认删除
            if messagebox.askyesno("确认删除", f"确定要删除 {item_name} 吗?\n文件将被移至回收站并在30天后自动删除。"):
                try:
                    # 确保回收站存在
                    os.makedirs(self.recycle_bin, exist_ok=True)
                    
                    # 处理重名文件
                    target_path = os.path.join(self.recycle_bin, item_name)
                    counter = 1
                    while os.path.exists(target_path):
                        name, ext = os.path.splitext(item_name)
                        target_path = os.path.join(self.recycle_bin, f"{name}_{counter}{ext}")
                        counter += 1
                    
                    # 移动到回收站
                    shutil.move(item_path, target_path)
                    
                    # 记录删除信息
                    self.record_recycle_info(os.path.basename(target_path), item_path)
                    
                    self.refresh_directory_tree()
                    
                    # 如果删除的是当前打开的文件，清空编辑区
                    if self.current_file == item_path:
                        self.new_file()
                except Exception as e:
                    messagebox.showerror("错误", f"删除失败: {str(e)}")
        except:
            messagebox.showwarning("警告", "请先选择一个项目")

    def record_recycle_info(self, recycled_name, original_path):
        """记录回收站文件信息"""
        # 读取现有信息
        recycle_data = {}
        if os.path.exists(self.recycle_info):
            try:
                with open(self.recycle_info, "r", encoding="utf-8") as f:
                    recycle_data = json.load(f)
            except:
                pass
        
        # 记录新信息：删除时间和原始路径
        recycle_data[recycled_name] = {
            "delete_time": datetime.datetime.now().timestamp(),
            "original_path": original_path
        }
        
        # 保存信息
        with open(self.recycle_info, "w", encoding="utf-8") as f:
            json.dump(recycle_data, f, ensure_ascii=False, indent=2)

    def show_item_properties(self):
        """显示项目属性"""
        try:
            selected_item = self.directory_tree.selection()[0]
            item_path = self.directory_tree.item(selected_item, "values")[0]
            item_type = self.directory_tree.item(selected_item, "values")[1]
            
            # 获取文件属性
            stats = os.stat(item_path)
            create_time = datetime.datetime.fromtimestamp(stats.st_ctime).strftime("%Y-%m-%d %H:%M:%S")
            modify_time = datetime.datetime.fromtimestamp(stats.st_mtime).strftime("%Y-%m-%d %H:%M:%S")
            
            # 显示属性对话框
            properties = f"名称: {os.path.basename(item_path)}\n"
            properties += f"类型: {'文件夹' if item_type == 'directory' else '文件'}\n"
            properties += f"路径: {item_path}\n"
            properties += f"创建时间: {create_time}\n"
            properties += f"修改时间: {modify_time}\n"
            
            if item_type == 'file':
                properties += f"大小: {stats.st_size} 字节\n"
            
            messagebox.showinfo("属性", properties)
        except:
            messagebox.showwarning("警告", "请先选择一个项目")

    def show_batch_operations(self):
        """显示批量操作对话框"""
        # 获取所有选中的项目
        selected_items = self.directory_tree.selection()
        if not selected_items:
            messagebox.showwarning("警告", "请先选择要操作的项目")
            return
        
        # 创建批量操作窗口
        batch_window = tk.Toplevel(self.root)
        batch_window.title("批量操作")
        batch_window.geometry("400x300")
        batch_window.transient(self.root)
        batch_window.grab_set()
        
        # 显示选中的项目数量
        tk.Label(batch_window, text=f"已选择 {len(selected_items)} 个项目:").pack(pady=10)
        
        # 批量移动
        def batch_move():
            target_dir = filedialog.askdirectory(title="选择目标目录", initialdir=self.storage_path.get())
            if target_dir:
                success_count = 0
                fail_count = 0
                for item in selected_items:
                    item_path = self.directory_tree.item(item, "values")[0]
                    item_name = os.path.basename(item_path)
                    target_path = os.path.join(target_dir, item_name)
                    
                    try:
                        if os.path.exists(target_path):
                            fail_count += 1
                            continue
                        shutil.move(item_path, target_path)
                        success_count += 1
                    except:
                        fail_count += 1
                
                messagebox.showinfo("操作完成", f"成功移动 {success_count} 个项目，失败 {fail_count} 个")
                self.refresh_directory_tree()
                batch_window.destroy()
        
        # 批量删除
        def batch_delete():
            if messagebox.askyesno("确认删除", f"确定要删除选中的 {len(selected_items)} 个项目吗?\n文件将被移至回收站并在30天后自动删除。"):
                success_count = 0
                fail_count = 0
                
                for item in selected_items:
                    item_path = self.directory_tree.item(item, "values")[0]
                    item_name = os.path.basename(item_path)
                    
                    try:
                        # 处理重名文件
                        target_path = os.path.join(self.recycle_bin, item_name)
                        counter = 1
                        while os.path.exists(target_path):
                            name, ext = os.path.splitext(item_name)
                            target_path = os.path.join(self.recycle_bin, f"{name}_{counter}{ext}")
                            counter += 1
                        
                        shutil.move(item_path, target_path)
                        # 记录删除信息
                        self.record_recycle_info(os.path.basename(target_path), item_path)
                        success_count += 1
                    except:
                        fail_count += 1
                
                messagebox.showinfo("操作完成", f"成功删除 {success_count} 个项目，失败 {fail_count} 个")
                self.refresh_directory_tree()
                batch_window.destroy()
        
        # 回收站
        def open_recycle_bin():
            self.show_recycle_bin()
        
        # 按钮
        ttk.Button(batch_window, text="批量移动", command=batch_move).pack(fill=tk.X, padx=50, pady=5)
        ttk.Button(batch_window, text="批量删除", command=batch_delete).pack(fill=tk.X, padx=50, pady=5)
        ttk.Button(batch_window, text="查看回收站", command=open_recycle_bin).pack(fill=tk.X, padx=50, pady=5)

    def check_encryption_password(self):
        """检查加密密码是否已设置，如未设置则要求用户设置"""
        # 检查密码文件是否存在
        if os.path.exists(self.password_file):
            with open(self.password_file, "r", encoding="utf-8") as f:
                self.password_hash = f.read().strip()
        else:
            # 第一次使用，要求设置密码
            self.set_encryption_password()

    def set_encryption_password(self):
        """设置加密密码"""
        # 创建密码设置窗口
        password_window = tk.Toplevel(self.root)
        password_window.title("设置加密密码")
        password_window.geometry("300x200")
        password_window.transient(self.root)
        password_window.grab_set()
        password_window.protocol("WM_DELETE_WINDOW", lambda: self.root.destroy())  # 不设置密码无法使用
        
        tk.Label(password_window, text="请设置加密密码:").pack(pady=10)
        password_entry = tk.Entry(password_window, show="*")
        password_entry.pack(pady=5, padx=20, fill=tk.X)
        
        tk.Label(password_window, text="请确认密码:").pack(pady=5)
        confirm_entry = tk.Entry(password_window, show="*")
        confirm_entry.pack(pady=5, padx=20, fill=tk.X)
        
        def save_password():
            password = password_entry.get()
            confirm = confirm_entry.get()
            
            if not password:
                messagebox.showerror("错误", "密码不能为空")
                return
                
            if password != confirm:
                messagebox.showerror("错误", "两次输入的密码不一致")
                return
                
            # 保存密码哈希
            self.password_hash = hashlib.sha256(password.encode()).hexdigest()
            with open(self.password_file, "w", encoding="utf-8") as f:
                f.write(self.password_hash)
            
            password_window.destroy()
        
        ttk.Button(password_window, text="确定", command=save_password).pack(pady=15)
        password_entry.focus_set()
        
        # 等待密码设置完成
        self.root.wait_window(password_window)

    def verify_password(self, password):
        """验证密码是否正确"""
        return hashlib.sha256(password.encode()).hexdigest() == self.password_hash

    def load_storage_settings(self):
        """加载存储设置"""
        settings_file = os.path.join(self.app_data_dir, "storage_settings.json")
        if os.path.exists(settings_file):
            try:
                with open(settings_file, "r", encoding="utf-8") as f:
                    settings = json.load(f)
                    if "storage_path" in settings and os.path.exists(settings["storage_path"]):
                        self.storage_path.set(settings["storage_path"])
            except:
                pass

    def save_storage_settings(self):
        """保存存储设置"""
        settings_file = os.path.join(self.app_data_dir, "storage_settings.json")
        settings = {
            "storage_path": self.storage_path.get()
        }
        with open(settings_file, "w", encoding="utf-8") as f:
            json.dump(settings, f, ensure_ascii=False, indent=2)

    def load_backup_settings(self):
        """加载备份设置"""
        settings_file = os.path.join(self.app_data_dir, "backup_settings.json")
        if os.path.exists(settings_file):
            try:
                with open(settings_file, "r", encoding="utf-8") as f:
                    settings = json.load(f)
                    if "backup_enabled" in settings:
                        self.backup_enabled.set(settings["backup_enabled"])
                    if "backup_interval" in settings:
                        self.backup_interval.set(settings["backup_interval"])
            except:
                pass

    def save_backup_settings(self):
        """保存备份设置"""
        settings_file = os.path.join(self.app_data_dir, "backup_settings.json")
        settings = {
            "backup_enabled": self.backup_enabled.get(),
            "backup_interval": self.backup_interval.get()
        }
        with open(settings_file, "w", encoding="utf-8") as f:
            json.dump(settings, f, ensure_ascii=False, indent=2)

    def create_menu(self):
        menubar = tk.Menu(self.root)
        
        # 文件菜单
        file_menu = tk.Menu(menubar, tearoff=0)
        
        # 创建"新建"子菜单
        new_menu = tk.Menu(file_menu, tearoff=0)
        new_menu.add_command(label="空白笔记", command=self.new_file)
        new_menu.add_command(label="待办模板", command=lambda: self.new_template("待办"))
        new_menu.add_command(label="日记模板", command=lambda: self.new_template("日记"))
        
        # 将新建子菜单添加到文件菜单
        file_menu.add_cascade(label="新建", menu=new_menu)
        file_menu.add_separator()
        file_menu.add_command(label="打开笔记", command=self.open_file)
        file_menu.add_command(label="最近打开", command=self.recent_files)
        file_menu.add_separator()
        file_menu.add_command(label="保存", command=self.save_file)
        file_menu.add_command(label="另存为", command=self.save_as_file)
        file_menu.add_separator()
        file_menu.add_command(label="关闭", command=self.close_file)
        file_menu.add_command(label="退出", command=self.on_closing)  # 绑定退出到关闭处理函数
        
        # 自动保存菜单
        auto_save_menu = tk.Menu(file_menu, tearoff=0)
        auto_save_menu.add_checkbutton(label="启用自动保存", variable=self.auto_save_enabled, 
                                      command=self.toggle_auto_save)
        file_menu.add_cascade(label="自动保存", menu=auto_save_menu)
        
        menubar.add_cascade(label="文件", menu=file_menu)
        
        # 编辑菜单
        edit_menu = tk.Menu(menubar, tearoff=0)
        edit_menu.add_command(label="撤销", command=lambda: self.text.event_generate("<<Undo>>"))
        edit_menu.add_command(label="重做", command=lambda: self.text.event_generate("<<Redo>>"))
        edit_menu.add_separator()
        edit_menu.add_command(label="剪切", command=lambda: self.text.event_generate("<<Cut>>"))
        edit_menu.add_command(label="复制", command=lambda: self.text.event_generate("<<Copy>>"))
        edit_menu.add_command(label="粘贴", command=self.paste)
        edit_menu.add_command(label="剪贴板历史", command=self.show_clipboard_history)
        edit_menu.add_separator()
        edit_menu.add_command(label="查找与替换", command=self.show_find_dialog)
        menubar.add_cascade(label="编辑", menu=edit_menu)
        
        # 格式菜单
        format_menu = tk.Menu(menubar, tearoff=0)
        
        # 字体设置
        font_menu = tk.Menu(format_menu, tearoff=0)
        font_menu.add_command(label="字体设置", command=self.show_font_dialog)
        font_menu.add_command(label="字体颜色", command=self.choose_font_color)  # 添加字体颜色选项
        format_menu.add_cascade(label="字体", menu=font_menu)
        
        # 样式设置（粗体、斜体、下划线）
        format_menu.add_checkbutton(label="粗体", variable=self.bold, 
                                   command=lambda: self.apply_format("bold"))
        format_menu.add_checkbutton(label="斜体", variable=self.italic, 
                                   command=lambda: self.apply_format("italic"))
        format_menu.add_checkbutton(label="下划线", variable=self.underline, 
                                   command=lambda: self.apply_format("underline"))
        
        menubar.add_cascade(label="格式", menu=format_menu)
        
        # 工具菜单
        tools_menu = tk.Menu(menubar, tearoff=0)
        tools_menu.add_command(label="存储设置", command=self.show_storage_settings)
        tools_menu.add_command(label="备份设置", command=self.show_backup_settings)
        tools_menu.add_command(label="更改加密密码", command=self.change_encryption_password)
        tools_menu.add_separator()
        tools_menu.add_command(label="回收站", command=self.show_recycle_bin)
        tools_menu.add_command(label="清空回收站", command=self.empty_recycle_bin)
        menubar.add_cascade(label="工具", menu=tools_menu)
        
        self.root.config(menu=menubar)

    def choose_font_color(self):
        """选择字体颜色"""
        # 打开颜色选择对话框
        color = colorchooser.askcolor(title="选择字体颜色", initialcolor=self.font_color.get())
        
        # 如果用户选择了颜色（没有点击取消）
        if color[1] is not None:
            self.font_color.set(color[1])
            # 应用颜色到选中文本或整个文本
            try:
                # 尝试获取选中文本范围
                start, end = self.text.tag_ranges(tk.SEL)
                # 创建或配置颜色标签
                tag_name = f"color_{color[1].replace('#', '')}"
                self.text.tag_configure(tag_name, foreground=color[1])
                self.text.tag_add(tag_name, start, end)
            except ValueError:
                # 没有选中文本，应用到整个文本
                self.text.configure(fg=color[1])

    def show_storage_settings(self):
        """显示存储设置窗口"""
        # 如果窗口已存在，先销毁
        if self.storage_settings_window and isinstance(self.storage_settings_window, tk.Toplevel) and self.storage_settings_window.winfo_exists():
            self.storage_settings_window.destroy()
            
        # 创建新窗口
        self.storage_settings_window = tk.Toplevel(self.root)
        self.storage_settings_window.title("存储设置")
        self.storage_settings_window.geometry("500x200")
        
        tk.Label(self.storage_settings_window, text="笔记存储位置:").grid(row=0, column=0, padx=5, pady=20, sticky="w")
        ttk.Entry(self.storage_settings_window, textvariable=self.storage_path, width=40).grid(row=0, column=1, padx=5, pady=20)
        ttk.Button(self.storage_settings_window, text="浏览", command=self.browse_storage_path).grid(row=0, column=2, padx=5, pady=20)
        
        ttk.Button(self.storage_settings_window, text="应用", command=self.apply_storage_settings).grid(row=1, column=1, pady=10)

    def browse_storage_path(self):
        """浏览选择存储路径"""
        path = filedialog.askdirectory(initialdir=self.storage_path.get())
        if path:
            self.storage_path.set(path)

    def apply_storage_settings(self):
        """应用存储设置"""
        # 确保目录存在
        os.makedirs(self.storage_path.get(), exist_ok=True)
        self.save_storage_settings()
        self.refresh_directory_tree()
        self.storage_settings_window.destroy()
        messagebox.showinfo("提示", "存储设置已应用")

    def show_backup_settings(self):
        """显示备份设置窗口"""
        # 如果窗口已存在，先销毁
        if self.backup_settings_window and isinstance(self.backup_settings_window, tk.Toplevel) and self.backup_settings_window.winfo_exists():
            self.backup_settings_window.destroy()
            
        # 创建新窗口
        self.backup_settings_window = tk.Toplevel(self.root)
        self.backup_settings_window.title("备份设置")
        self.backup_settings_window.geometry("400x200")
        
        ttk.Checkbutton(self.backup_settings_window, text="启用自动备份", variable=self.backup_enabled).grid(row=0, column=0, columnspan=2, padx=5, pady=20, sticky="w")
        
        tk.Label(self.backup_settings_window, text="备份频率:").grid(row=1, column=0, padx=5, pady=10, sticky="w")
        backup_options = [("每天", "daily"), ("每周", "weekly")]
        backup_combobox = ttk.Combobox(self.backup_settings_window, textvariable=self.backup_interval, state="readonly", width=10)
        backup_combobox['values'] = [opt[0] for opt in backup_options]
        backup_combobox.current(0)
        backup_combobox.grid(row=1, column=1, padx=5, pady=10, sticky="w")
        
        ttk.Button(self.backup_settings_window, text="应用", command=self.apply_backup_settings).grid(row=2, column=1, pady=10)

    def apply_backup_settings(self):
        """应用备份设置"""
        self.save_backup_settings()
        self.backup_settings_window.destroy()
        messagebox.showinfo("提示", "备份设置已应用")

    def change_encryption_password(self):
        """更改加密密码"""
        # 创建密码更改窗口
        password_window = tk.Toplevel(self.root)
        password_window.title("更改加密密码")
        password_window.geometry("300x250")
        password_window.transient(self.root)
        password_window.grab_set()
        
        tk.Label(password_window, text="当前密码:").pack(pady=5)
        current_entry = tk.Entry(password_window, show="*")
        current_entry.pack(padx=20, fill=tk.X)
        
        tk.Label(password_window, text="新密码:").pack(pady=5)
        new_entry = tk.Entry(password_window, show="*")
        new_entry.pack(padx=20, fill=tk.X)
        
        tk.Label(password_window, text="确认新密码:").pack(pady=5)
        confirm_entry = tk.Entry(password_window, show="*")
        confirm_entry.pack(padx=20, fill=tk.X)
        
        def save_new_password():
            current_password = current_entry.get()
            new_password = new_entry.get()
            confirm_password = confirm_entry.get()
            
            # 验证当前密码
            if not self.verify_password(current_password):
                messagebox.showerror("错误", "当前密码不正确")
                return
                
            if not new_password:
                messagebox.showerror("错误", "新密码不能为空")
                return
                
            if new_password != confirm_password:
                messagebox.showerror("错误", "两次输入的新密码不一致")
                return
                
            # 更新密码
            self.password_hash = hashlib.sha256(new_password.encode()).hexdigest()
            with open(self.password_file, "w", encoding="utf-8") as f:
                f.write(self.password_hash)
            
            # 更新加密器
            self.cipher = AESCipher(new_password)
            
            # 重新加密所有文件
            self.reencrypt_all_files()
            
            password_window.destroy()
            messagebox.showinfo("成功", "密码已更改，所有文件已重新加密")
        
        ttk.Button(password_window, text="确定", command=save_new_password).pack(pady=15)
        current_entry.focus_set()

    def reencrypt_all_files(self):
        """重新加密所有文件（当密码更改时）"""
        # 遍历所有笔记文件并重新加密
        for root, dirs, files in os.walk(self.storage_path.get()):
            for file in files:
                if file.lower().endswith(('.txt', '.md')):
                    file_path = os.path.join(root, file)
                    try:
                        # 先解密
                        with open(file_path, "r", encoding="utf-8") as f:
                            encrypted_content = f.read()
                        content = self.cipher.decrypt(encrypted_content)
                        
                        # 再加密
                        new_encrypted_content = self.cipher.encrypt(content)
                        with open(file_path, "w", encoding="utf-8") as f:
                            f.write(new_encrypted_content)
                    except:
                        print(f"重新加密文件 {file_path} 失败")

    def show_recycle_bin(self):
        """显示回收站窗口"""
        # 如果窗口已存在，先销毁
        if self.recycle_bin_window and isinstance(self.recycle_bin_window, tk.Toplevel) and self.recycle_bin_window.winfo_exists():
            self.recycle_bin_window.destroy()
            
        # 创建新窗口
        self.recycle_bin_window = tk.Toplevel(self.root)
        self.recycle_bin_window.title("回收站")
        self.recycle_bin_window.geometry("600x400")
        self.recycle_bin_window.transient(self.root)
        
        # 创建回收站文件列表
        frame = ttk.Frame(self.recycle_bin_window)
        frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
        
        # 创建列表视图
        columns = ("name", "delete_time", "original_path")
        self.recycle_tree = ttk.Treeview(frame, columns=columns, show="headings")
        
        # 设置列标题
        self.recycle_tree.heading("name", text="文件名")
        self.recycle_tree.heading("delete_time", text="删除时间")
        self.recycle_tree.heading("original_path", text="原始位置")
        
        # 设置列宽
        self.recycle_tree.column("name", width=150)
        self.recycle_tree.column("delete_time", width=150)
        self.recycle_tree.column("original_path", width=300)
        
        # 添加滚动条
        scrollbar = ttk.Scrollbar(frame, orient="vertical", command=self.recycle_tree.yview)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.recycle_tree.configure(yscrollcommand=scrollbar.set)
        
        self.recycle_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        
        # 加载回收站文件
        self.load_recycle_bin_items()
        
        # 添加操作按钮
        button_frame = ttk.Frame(self.recycle_bin_window)
        button_frame.pack(fill=tk.X, padx=10, pady=10)
        
        ttk.Button(button_frame, text="恢复选中项", command=self.restore_selected).pack(side=tk.LEFT, padx=5)
        ttk.Button(button_frame, text="删除选中项", command=self.permanently_delete_selected).pack(side=tk.LEFT, padx=5)
        ttk.Button(button_frame, text="清空回收站", command=self.empty_recycle_bin).pack(side=tk.RIGHT, padx=5)

    def load_recycle_bin_items(self):
        """加载回收站项目"""
        # 清空现有内容
        for item in self.recycle_tree.get_children():
            self.recycle_tree.delete(item)
            
        # 读取回收站信息
        recycle_data = {}
        if os.path.exists(self.recycle_info):
            try:
                with open(self.recycle_info, "r", encoding="utf-8") as f:
                    recycle_data = json.load(f)
            except:
                pass
        
        # 添加项目到列表
        for name, info in recycle_data.items():
            file_path = os.path.join(self.recycle_bin, name)
            if os.path.exists(file_path):
                delete_time = datetime.datetime.fromtimestamp(info["delete_time"]).strftime("%Y-%m-%d %H:%M")
                original_path = info["original_path"]
                
                # 计算剩余天数
                days_passed = (datetime.datetime.now().timestamp() - info["delete_time"]) / (24 * 3600)
                days_left = 30 - days_passed
                
                # 在文件名后显示剩余天数
                display_name = f"{name} ({int(days_left)}天剩余)"
                self.recycle_tree.insert("", tk.END, text=name, 
                                       values=(display_name, delete_time, original_path))

    def restore_selected(self):
        """恢复选中的回收站项目"""
        selected_items = self.recycle_tree.selection()
        if not selected_items:
            messagebox.showwarning("警告", "请先选择要恢复的项目")
            return
            
        # 读取回收站信息
        recycle_data = {}
        if os.path.exists(self.recycle_info):
            try:
                with open(self.recycle_info, "r", encoding="utf-8") as f:
                    recycle_data = json.load(f)
            except:
                pass
        
        success_count = 0
        fail_count = 0
        
        for item in selected_items:
            name = self.recycle_tree.item(item, "text")
            if name in recycle_data:
                try:
                    # 获取原始路径
                    original_path = recycle_data[name]["original_path"]
                    recycle_path = os.path.join(self.recycle_bin, name)
                    
                    # 检查原始目录是否存在
                    original_dir = os.path.dirname(original_path)
                    if not os.path.exists(original_dir):
                        os.makedirs(original_dir, exist_ok=True)
                    
                    # 处理重名文件
                    if os.path.exists(original_path):
                        name_part, ext = os.path.splitext(os.path.basename(original_path))
                        counter = 1
                        while os.path.exists(f"{name_part}_{counter}{ext}"):
                            counter += 1
                        original_path = f"{name_part}_{counter}{ext}"
                    
                    # 移动文件
                    shutil.move(recycle_path, original_path)
                    
                    # 从回收站信息中移除
                    del recycle_data[name]
                    
                    success_count += 1
                except Exception as e:
                    print(f"恢复文件失败: {str(e)}")
                    fail_count += 1
        
        # 保存更新后的回收站信息
        with open(self.recycle_info, "w", encoding="utf-8") as f:
            json.dump(recycle_data, f, ensure_ascii=False, indent=2)
        
        # 刷新回收站列表
        self.load_recycle_bin_items()
        # 刷新主目录树
        self.refresh_directory_tree()
        
        messagebox.showinfo("操作完成", f"成功恢复 {success_count} 个项目，失败 {fail_count} 个")

    def permanently_delete_selected(self):
        """永久删除选中的回收站项目"""
        selected_items = self.recycle_tree.selection()
        if not selected_items:
            messagebox.showwarning("警告", "请先选择要删除的项目")
            return
            
        if not messagebox.askyesno("确认删除", "确定要永久删除选中的项目吗？此操作不可恢复。"):
            return
            
        # 读取回收站信息
        recycle_data = {}
        if os.path.exists(self.recycle_info):
            try:
                with open(self.recycle_info, "r", encoding="utf-8") as f:
                    recycle_data = json.load(f)
            except:
                pass
        
        success_count = 0
        fail_count = 0
        
        for item in selected_items:
            name = self.recycle_tree.item(item, "text")
            try:
                # 删除文件
                file_path = os.path.join(self.recycle_bin, name)
                if os.path.isdir(file_path):
                    shutil.rmtree(file_path)
                else:
                    os.remove(file_path)
                
                # 从回收站信息中移除
                if name in recycle_data:
                    del recycle_data[name]
                
                success_count += 1
            except Exception as e:
                print(f"删除文件失败: {str(e)}")
                fail_count += 1
        
        # 保存更新后的回收站信息
        with open(self.recycle_info, "w", encoding="utf-8") as f:
            json.dump(recycle_data, f, ensure_ascii=False, indent=2)
        
        # 刷新回收站列表
        self.load_recycle_bin_items()
        
        messagebox.showinfo("操作完成", f"成功删除 {success_count} 个项目，失败 {fail_count} 个")

    def empty_recycle_bin(self):
        """清空回收站"""
        if not os.path.exists(self.recycle_bin) or not os.listdir(self.recycle_bin):
            messagebox.showinfo("提示", "回收站为空")
            return
            
        if messagebox.askyesno("确认清空", "确定要清空回收站吗？所有项目将被永久删除，不可恢复。"):
            try:
                # 读取回收站信息
                recycle_data = {}
                if os.path.exists(self.recycle_info):
                    try:
                        with open(self.recycle_info, "r", encoding="utf-8") as f:
                            recycle_data = json.load(f)
                    except:
                        pass
                
                # 删除所有文件
                for name in recycle_data.keys():
                    file_path = os.path.join(self.recycle_bin, name)
                    if os.path.exists(file_path):
                        if os.path.isdir(file_path):
                            shutil.rmtree(file_path)
                        else:
                            os.remove(file_path)
                
                # 清空回收站信息
                with open(self.recycle_info, "w", encoding="utf-8") as f:
                    json.dump({}, f)
                
                # 如果回收站窗口打开，刷新列表
                if self.recycle_bin_window and isinstance(self.recycle_bin_window, tk.Toplevel) and self.recycle_bin_window.winfo_exists():
                    self.load_recycle_bin_items()
                
                messagebox.showinfo("成功", "回收站已清空")
            except Exception as e:
                messagebox.showerror("错误", f"清空回收站失败: {str(e)}")

    def start_recycle_bin_cleaner(self):
        """启动回收站清理线程，自动删除30天前的文件"""
        threading.Thread(target=self.recycle_bin_cleaner_loop, daemon=True).start()

    def recycle_bin_cleaner_loop(self):
        """回收站清理循环"""
        while True:
            # 每天检查一次
            time.sleep(24 * 3600)
            self.clean_old_recycle_items()

    def clean_old_recycle_items(self):
        """清理回收站中超过30天的项目"""
        try:
            # 读取回收站信息
            recycle_data = {}
            if os.path.exists(self.recycle_info):
                try:
                    with open(self.recycle_info, "r", encoding="utf-8") as f:
                        recycle_data = json.load(f)
                except:
                    pass
            
            # 计算30天前的时间戳
            thirty_days_ago = datetime.datetime.now().timestamp() - (30 * 24 * 3600)
            
            # 要删除的项目
            to_delete = []
            
            for name, info in recycle_data.items():
                if info["delete_time"] < thirty_days_ago:
                    to_delete.append(name)
            
            # 删除过期项目
            for name in to_delete:
                try:
                    file_path = os.path.join(self.recycle_bin, name)
                    if os.path.exists(file_path):
                        if os.path.isdir(file_path):
                            shutil.rmtree(file_path)
                        else:
                            os.remove(file_path)
                    
                    del recycle_data[name]
                except:
                    continue
            
            # 保存更新后的回收站信息
            with open(self.recycle_info, "w", encoding="utf-8") as f:
                json.dump(recycle_data, f, ensure_ascii=False, indent=2)
                
            print(f"回收站清理完成，已删除 {len(to_delete)} 个过期项目")
        except Exception as e:
            print(f"回收站清理失败: {str(e)}")

    # 以下是文件操作和其他功能的实现
    def new_file(self):
        """创建新文件"""
        self.text.delete(1.0, tk.END)
        self.current_file = None
        self.root.title("增强版记事本 - 未命名")

    def open_file(self):
        """打开文件"""
        file_path = filedialog.askopenfilename(
            defaultextension=".txt",
            filetypes=[("文本文件", "*.txt"), ("Markdown文件", "*.md"), ("所有文件", "*.*")],
            initialdir=self.storage_path.get()
        )
        if file_path:
            self.open_file_from_path(file_path)

    def open_file_from_path(self, file_path):
        """从路径打开文件"""
        try:
            with open(file_path, "r", encoding="utf-8") as file:
                content = file.read()
            
            # 尝试解密内容
            decrypted = False
            if self.cipher:
                try:
                    # 尝试解密，如果成功则使用解密后的内容
                    content = self.cipher.decrypt(content)
                    decrypted = True
                except:
                    # 解密失败，可能是未加密的文件，直接使用原内容
                    # 询问用户是否要加密此文件
                    if messagebox.askyesno("加密提示", "检测到这是一个未加密的文件，是否加密后再打开？"):
                        # 先保存原内容
                        original_content = content
                        # 清空并重新加载加密后的内容
                        self.text.delete(1.0, tk.END)
                        self.text.insert(tk.END, original_content)
                        self.current_file = file_path
                        self.save_file()  # 保存时会自动加密
                        # 重新打开已加密的文件
                        return self.open_file_from_path(file_path)
            
            self.text.delete(1.0, tk.END)
            self.text.insert(tk.END, content)
            self.current_file = file_path
            self.root.title(f"增强版记事本 - {os.path.basename(file_path)}")
            
            # 记录到最近打开文件
            self.add_to_recent_files(file_path)
        except Exception as e:
            messagebox.showerror("错误", f"无法打开文件: {str(e)}")

    def save_file(self):
        """保存文件，确保加密后在外部打开显示为乱码"""
        if self.current_file:
            try:
                content = self.text.get(1.0, tk.END)[:-1]  # 排除最后一个换行符
                
                # 使用AES-256加密内容，确保在外部打开显示为乱码
                if self.cipher:
                    content = self.cipher.encrypt(content)
                
                with open(self.current_file, "w", encoding="utf-8") as file:
                    file.write(content)
                return True
            except Exception as e:
                messagebox.showerror("错误", f"保存文件失败: {str(e)}")
                return False
        else:
            return self.save_as_file()

    def save_as_file(self):
        """另存为文件"""
        file_path = filedialog.asksaveasfilename(
            defaultextension=".txt",
            filetypes=[("文本文件", "*.txt"), ("Markdown文件", "*.md"), ("所有文件", "*.*")],
            initialdir=self.storage_path.get()
        )
        if file_path:
            self.current_file = file_path
            self.root.title(f"增强版记事本 - {os.path.basename(file_path)}")
            return self.save_file()
        return False

    def close_file(self):
        """关闭文件"""
        if messagebox.askyesno("确认", "确定要关闭当前文件吗？未保存的更改将会丢失。"):
            self.new_file()

    def on_closing(self):
        """窗口关闭处理"""
        self.running = False  # 停止自动保存线程
        if self.auto_save_thread and self.auto_save_thread.is_alive():
            self.auto_save_thread.join()
        
        self.root.destroy()

    def start_auto_save(self):
        """启动自动保存线程"""
        self.running = True
        self.auto_save_thread = threading.Thread(target=self.auto_save_loop, daemon=True)
        self.auto_save_thread.start()

    def auto_save_loop(self):
        """自动保存循环"""
        while self.running:
            if self.auto_save_enabled.get() and self.current_file:
                # 在主线程中执行保存操作
                self.root.after(0, self.save_file)
            
            # 等待指定的时间间隔（分钟转换为秒）
            for _ in range(self.auto_save_interval.get() * 60):
                if not self.running:
                    return
                time.sleep(1)

    def toggle_auto_save(self):
        """切换自动保存状态"""
        if self.auto_save_enabled.get():
            messagebox.showinfo("提示", f"自动保存已启用，间隔为 {self.auto_save_interval.get()} 分钟")

    def apply_auto_save_settings(self):
        """应用自动保存设置"""
        self.auto_save_window.destroy()
        messagebox.showinfo("提示", f"自动保存设置已应用，间隔为 {self.auto_save_interval.get()} 分钟")

    def show_font_dialog(self):
        """显示字体设置对话框"""
        font_window = tk.Toplevel(self.root)
        font_window.title("字体设置")
        font_window.geometry("300x200")
        font_window.transient(self.root)
        font_window.grab_set()
        
        # 字体选择
        tk.Label(font_window, text="字体:").grid(row=0, column=0, padx=5, pady=10, sticky="w")
        fonts = ["SimSun", "Microsoft YaHei", "SimHei", "KaiTi", "FangSong"]
        font_combobox = ttk.Combobox(font_window, textvariable=self.font_family, values=fonts, state="readonly")
        font_combobox.grid(row=0, column=1, padx=5, pady=10)
        
        # 字号选择
        tk.Label(font_window, text="字号:").grid(row=1, column=0, padx=5, pady=10, sticky="w")
        size_combobox = ttk.Combobox(font_window, textvariable=self.font_size, 
                                    values=[8, 10, 12, 14, 16, 18, 20, 24], state="readonly")
        size_combobox.grid(row=1, column=1, padx=5, pady=10)
        
        def apply_font():
            weight = "bold" if self.bold.get() else "normal"
            slant = "italic" if self.italic.get() else "roman"
            
            self.text.configure(
                font=(self.font_family.get(), self.font_size.get(), weight, slant),
                fg=self.font_color.get()
            )
            font_window.destroy()
        
        ttk.Button(font_window, text="应用", command=apply_font).grid(row=2, column=1, pady=10)

    def apply_format(self, format_type):
        """应用文本格式"""
        # 获取选中的文本范围
        try:
            start, end = self.text.tag_ranges(tk.SEL)
        except ValueError:
            # 如果没有选中文本，提示用户
            messagebox.showinfo("提示", "请先选中要设置格式的文本")
            return
        
        # 应用或移除格式
        if format_type == "bold":
            if self.bold.get():
                self.text.tag_add("bold", start, end)
            else:
                self.text.tag_remove("bold", start, end)
        elif format_type == "italic":
            if self.italic.get():
                self.text.tag_add("italic", start, end)
            else:
                self.text.tag_remove("italic", start, end)
        elif format_type == "underline":
            if self.underline.get():
                self.text.tag_add("underline", start, end)
            else:
                self.text.tag_remove("underline", start, end)

    def show_find_dialog(self):
        """显示查找替换对话框"""
        # 如果已有查找窗口，先销毁
        if self.find_window and isinstance(self.find_window, tk.Toplevel) and self.find_window.winfo_exists():
            self.find_window.destroy()
        
        self.find_window = tk.Toplevel(self.root)
        self.find_window.title("查找与替换")
        self.find_window.geometry("400x150")
        self.find_window.transient(self.root)
        self.find_window.grab_set()
        
        # 查找框
        tk.Label(self.find_window, text="查找:").grid(row=0, column=0, padx=5, pady=5, sticky="e")
        self.find_entry = tk.Entry(self.find_window, width=30)
        self.find_entry.grid(row=0, column=1, padx=5, pady=5)
        self.find_entry.focus_set()
        
        # 替换框
        tk.Label(self.find_window, text="替换为:").grid(row=1, column=0, padx=5, pady=5, sticky="e")
        self.replace_entry = tk.Entry(self.find_window, width=30)
        self.replace_entry.grid(row=1, column=1, padx=5, pady=5)
        
        # 按钮
        button_frame = ttk.Frame(self.find_window)
        button_frame.grid(row=2, column=1, pady=10, sticky="e")
        
        ttk.Button(button_frame, text="查找下一个", command=self.find_next).pack(side=tk.LEFT, padx=5)
        ttk.Button(button_frame, text="替换", command=self.replace_current).pack(side=tk.LEFT, padx=5)
        ttk.Button(button_frame, text="全部替换", command=self.replace_all).pack(side=tk.LEFT, padx=5)

    def find_next(self):
        """查找下一个匹配项，修复无限提示问题"""
        # 清除之前的高亮
        self.text.tag_remove("search_highlight", "1.0", tk.END)
        
        # 获取查找文本
        find_text = self.find_entry.get()
        if not find_text:
            return
        
        # 从当前位置开始查找
        start_pos = self.find_pos
        end_pos = self.text.index(f"{start_pos}+{len(find_text)}c")
        
        # 在文本中查找
        pos = self.text.search(find_text, start_pos, stopindex=tk.END)
        if pos:
            # 记录新的起始位置
            self.find_pos = f"{pos}+{len(find_text)}c"
            
            # 高亮显示找到的文本
            end_pos = f"{pos}+{len(find_text)}c"
            self.text.tag_add("search_highlight", pos, end_pos)
            
            # 滚动到找到的位置
            self.text.see(pos)
            
            # 选中找到的文本
            self.text.tag_remove(tk.SEL, "1.0", tk.END)
            self.text.tag_add(tk.SEL, pos, end_pos)
        else:
            # 如果到达末尾，提示一次并重置查找位置
            messagebox.showinfo("提示", "已到达文档末尾，没有找到更多匹配项")
            self.find_pos = "1.0"  # 重置查找位置，但不再自动重新查找

    def replace_current(self):
        """替换当前选中的匹配项"""
        # 检查是否有选中的文本
        try:
            start, end = self.text.tag_ranges(tk.SEL)
        except ValueError:
            # 如果没有选中的文本，先查找下一个
            self.find_next()
            return
        
        # 替换文本
        replace_text = self.replace_entry.get()
        self.text.delete(start, end)
        self.text.insert(start, replace_text)
        
        # 继续查找下一个
        self.find_pos = start
        self.find_next()

    def replace_all(self):
        """替换所有匹配项"""
        find_text = self.find_entry.get()
        replace_text = self.replace_entry.get()
        
        if not find_text:
            return
        
        # 清除之前的高亮
        self.text.tag_remove("search_highlight", "1.0", tk.END)
        
        # 从头开始查找和替换
        count = 0
        pos = "1.0"
        
        while True:
            pos = self.text.search(find_text, pos, stopindex=tk.END)
            if not pos:
                break
            
            count += 1
            end_pos = f"{pos}+{len(find_text)}c"
            self.text.delete(pos, end_pos)
            self.text.insert(pos, replace_text)
            pos = f"{pos}+{len(replace_text)}c"
        
        messagebox.showinfo("完成", f"共替换了 {count} 处匹配项")
        self.find_window.destroy()

    def paste(self):
        """粘贴并记录剪贴板历史"""
        # 获取剪贴板内容
        try:
            content = self.root.clipboard_get()
            if content:
                # 添加到剪贴板历史
                if content not in self.clipboard_history:
                    self.clipboard_history.insert(0, content)
                    # 保持历史记录长度
                    if len(self.clipboard_history) > self.max_clip_history:
                        self.clipboard_history.pop()
                
                # 执行粘贴
                self.text.event_generate("<<Paste>>")
        except:
            # 剪贴板为空或不可访问
            pass

    def show_clipboard_history(self):
        """显示剪贴板历史"""
        if not self.clipboard_history:
            messagebox.showinfo("剪贴板历史", "剪贴板历史为空")
            return
        
        history_window = tk.Toplevel(self.root)
        history_window.title("剪贴板历史")
        history_window.geometry("400x300")
        history_window.transient(self.root)
        
        # 创建列表框显示历史记录
        history_listbox = tk.Listbox(history_window, width=50, height=15)
        history_listbox.pack(padx=10, pady=10, fill=tk.BOTH, expand=True)
        
        # 添加滚动条
        scrollbar = ttk.Scrollbar(history_listbox, orient="vertical", command=history_listbox.yview)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        history_listbox.configure(yscrollcommand=scrollbar.set)
        
        # 填充历史记录
        for i, item in enumerate(self.clipboard_history):
            # 显示前50个字符
            display_text = item[:50] + ("..." if len(item) > 50 else "")
            history_listbox.insert(tk.END, display_text)
        
        # 双击条目粘贴
        def paste_selected(event):
            index = history_listbox.curselection()
            if index:
                content = self.clipboard_history[index[0]]
                self.root.clipboard_clear()
                self.root.clipboard_append(content)
                self.text.event_generate("<<Paste>>")
                history_window.destroy()
        
        history_listbox.bind("<Double-1>", paste_selected)

    def new_template(self, template_type):
        """创建模板文件"""
        self.new_file()
        
        if template_type == "待办":
            date = datetime.datetime.now().strftime("%Y-%m-%d")
            content = f"# 待办清单 - {date}\n\n"
            content += "## 今日任务\n"
            content += "- [ ] 任务1\n"
            content += "- [ ] 任务2\n"
            content += "- [ ] 任务3\n\n"
            content += "## 已完成任务\n"
            content += "- [x] 已完成任务1\n\n"
            content += "## 备注\n"
        elif template_type == "日记":
            date = datetime.datetime.now().strftime("%Y-%m-%d")
            week_day = ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"][datetime.datetime.now().weekday()]
            content = f"# 日记 - {date} {week_day}\n\n"
            content += "## 今日总结\n\n"
            content += "## 重要事件\n\n"
            content += "## 感想与思考\n\n"
            content += "## 明日计划\n"
        
        self.text.insert(tk.END, content)

    def add_to_recent_files(self, file_path):
        """添加文件到最近打开列表"""
        recent_file = os.path.join(self.app_data_dir, "recent_files.json")
        recent_files = []
        
        # 读取现有列表
        if os.path.exists(recent_file):
            try:
                with open(recent_file, "r", encoding="utf-8") as f:
                    recent_files = json.load(f)
            except:
                pass
        
        # 如果文件已在列表中，先移除
        recent_files = [f for f in recent_files if f != file_path]
        
        # 添加到列表开头
        recent_files.insert(0, file_path)
        
        # 限制列表长度
        if len(recent_files) > 10:
            recent_files = recent_files[:10]
        
        # 保存列表
        with open(recent_file, "w", encoding="utf-8") as f:
            json.dump(recent_files, f, ensure_ascii=False)

    def recent_files(self):
        """显示最近打开的文件"""
        recent_file = os.path.join(self.app_data_dir, "recent_files.json")
        recent_files = []
        
        # 读取列表
        if os.path.exists(recent_file):
            try:
                with open(recent_file, "r", encoding="utf-8") as f:
                    recent_files = json.load(f)
            except:
                pass
        
        if not recent_files:
            messagebox.showinfo("最近打开", "没有最近打开的文件")
            return
        
        # 创建最近文件窗口
        recent_window = tk.Toplevel(self.root)
        recent_window.title("最近打开的文件")
        recent_window.geometry("400x300")
        recent_window.transient(self.root)
        
        # 创建列表框
        recent_listbox = tk.Listbox(recent_window, width=50, height=15)
        recent_listbox.pack(padx=10, pady=10, fill=tk.BOTH, expand=True)
        
        # 添加滚动条
        scrollbar = ttk.Scrollbar(recent_listbox, orient="vertical", command=recent_listbox.yview)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        recent_listbox.configure(yscrollcommand=scrollbar.set)
        
        # 填充列表
        for file_path in recent_files:
            if os.path.exists(file_path):
                recent_listbox.insert(tk.END, os.path.basename(file_path))
                # 存储完整路径作为列表项的附加数据
                recent_listbox.file_paths = recent_files
            else:
                # 文件不存在，显示标记
                recent_listbox.insert(tk.END, f"{os.path.basename(file_path)} (已删除)")
        
        # 双击打开文件
        def open_selected(event):
            index = recent_listbox.curselection()
            if index:
                file_path = recent_listbox.file_paths[index[0]]
                if os.path.exists(file_path):
                    self.open_file_from_path(file_path)
                    recent_window.destroy()
        
        recent_listbox.bind("<Double-1>", open_selected)

    def bind_shortcuts(self):
        """绑定快捷键"""
        # 文件操作
        self.root.bind("<Control-n>", lambda event: self.new_file())
        self.root.bind("<Control-o>", lambda event: self.open_file())
        self.root.bind("<Control-s>", lambda event: self.save_file())
        self.root.bind("<Control-Shift-S>", lambda event: self.save_as_file())
        
        # 编辑操作
        self.root.bind("<Control-f>", lambda event: self.show_find_dialog())
        
        # 格式操作
        self.root.bind("<Control-b>", lambda event: self.toggle_bold())
        self.root.bind("<Control-i>", lambda event: self.toggle_italic())

    def toggle_bold(self):
        """切换粗体状态"""
        self.bold.set(not self.bold.get())
        self.apply_format("bold")

    def toggle_italic(self):
        """切换斜体状态"""
        self.italic.set(not self.italic.get())
        self.apply_format("italic")

    def start_backup_monitor(self):
        """启动备份监控线程"""
        if self.backup_enabled.get():
            threading.Thread(target=self.backup_loop, daemon=True).start()

    def backup_loop(self):
        """备份循环"""
        while True:
            if self.backup_enabled.get():
                # 执行备份
                self.perform_backup()
                
                # 根据备份间隔等待
                if self.backup_interval.get() == "daily":
                    # 每天备份一次
                    time.sleep(24 * 60 * 60)
                else:  # weekly
                    # 每周备份一次
                    time.sleep(7 * 24 * 60 * 60)
            else:
                # 如果备份被禁用，每小时检查一次状态
                time.sleep(3600)

    def perform_backup(self):
        """执行备份"""
        try:
            # 创建带时间戳的备份目录
            timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
            backup_dir = os.path.join(self.backup_dir, timestamp)
            os.makedirs(backup_dir, exist_ok=True)
            
            # 复制所有笔记文件到备份目录
            for root, dirs, files in os.walk(self.storage_path.get()):
                # 创建对应的目录结构
                rel_path = os.path.relpath(root, self.storage_path.get())
                target_dir = os.path.join(backup_dir, rel_path)
                os.makedirs(target_dir, exist_ok=True)
                
                # 复制文件
                for file in files:
                    if file.lower().endswith(('.txt', '.md')):
                        src = os.path.join(root, file)
                        dst = os.path.join(target_dir, file)
                        shutil.copy2(src, dst)
            
            # 保留最近10个备份，删除更早的
            self.clean_old_backups()
            
            print(f"备份完成: {timestamp}")
        except Exception as e:
            print(f"备份失败: {str(e)}")

    def clean_old_backups(self):
        """清理旧备份"""
        try:
            # 获取所有备份目录并按时间排序
            backup_dirs = [d for d in os.listdir(self.backup_dir) 
                         if os.path.isdir(os.path.join(self.backup_dir, d))]
            backup_dirs.sort(reverse=True)  # 最新的在前
            
            # 保留最近10个备份
            if len(backup_dirs) > 10:
                for old_dir in backup_dirs[10:]:
                    shutil.rmtree(os.path.join(self.backup_dir, old_dir))
        except Exception as e:
            print(f"清理旧备份失败: {str(e)}")

if __name__ == "__main__":
    root = tk.Tk()
    app = NoteBook(root)
    root.mainloop()
